package com.tencent.bk.sdk.iam.helper;

import com.tencent.bk.sdk.iam.config.IamConfiguration;
import com.tencent.bk.sdk.iam.dto.ExpressionWithResourceDTO;
import com.tencent.bk.sdk.iam.dto.InstanceDTO;
import com.tencent.bk.sdk.iam.dto.action.ActionDTO;
import com.tencent.bk.sdk.iam.dto.expression.ExpressionDTO;
import com.tencent.bk.sdk.iam.dto.resource.ResourceDTO;
import com.tencent.bk.sdk.iam.dto.response.AuthResultDTO;
import com.tencent.bk.sdk.iam.exception.IamException;
import com.tencent.bk.sdk.iam.service.PolicyService;
import com.tencent.bk.sdk.iam.service.TokenService;
import org.apache.commons.codec.Charsets;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public class AuthHelper {
    private static final Logger log = LoggerFactory.getLogger(AuthHelper.class);
    private static final String BASIC_AUTH = "Basic";
    private static final String DIGEST_AUTH = "Digest";
    private static final String IAM_USER = "bk_iam";
    private static final String PATH_ATTRIBUTE = "_bk_iam_path_";
    private static final Integer BATCH_SIZE = 500;
    private final TokenService tokenService;
    private final PolicyService policyService;
    private final IamConfiguration iamConfiguration;

    public AuthHelper(TokenService tokenService, PolicyService policyService, IamConfiguration iamConfiguration) {
        this.tokenService = tokenService;
        this.policyService = policyService;
        this.iamConfiguration = iamConfiguration;
    }

    private static List<ResourceDTO> buildResourceList(List<InstanceDTO> instanceList) {
        return CollectionUtils.isEmpty(instanceList) ? null : (List)instanceList.parallelStream().map(AuthHelper::buildResource).collect(Collectors.toList());
    }

    private static ResourceDTO buildResource(InstanceDTO instance) {
        return instance != null ? ResourceDTO.builder().id(instance.getId()).system(instance.getSystem()).type(instance.getType()).attribute(instance.getAttribute()).build() : null;
    }

    protected static boolean calculateExpression(Map<String, InstanceDTO> instanceMap, ExpressionDTO expression) {
        if (expression.isEmpty()) {
            return false;
        } else {
            String type = null;
            String attribute = null;
            List<String> attributeValueList = Collections.emptyList();
            if (StringUtils.isNotBlank(expression.getField())) {
                try {
                    int lastIndex = expression.getField().lastIndexOf(".");
                    type = expression.getField().substring(0,lastIndex);
                    attribute = expression.getField().substring(lastIndex + 1);
                } catch (Exception e) {
                    throw new IamException(-1L, "Unsupported field " + expression.getField());
                }

                attributeValueList = getAttribute(attribute, (InstanceDTO)instanceMap.get(type));
            }

            switch (expression.getOperator()) {
                case NOT:
                    //NOT只有一个表达式，只会循环一次，取循环第一个结果判断
                    for (ExpressionDTO subExpression:expression.getContent()) {
                        boolean isAuth = calculateExpression(instanceMap, subExpression);
                        if(isAuth) {
                            return false;
                        } else {
                            return true;
                        }
                    }
                case OR:
                    for (ExpressionDTO subExpression:expression.getContent()) {
                        if(calculateExpression(instanceMap, subExpression)) {
                            return true;
                        }
                    }
                    return false;
                case AND:
                    for (ExpressionDTO subExpression:expression.getContent()) {
                        if(!calculateExpression(instanceMap, subExpression)) {
                            return false;
                        }
                    }
                    return true;
                case EQUAL:
                    checkParam(type, attribute, expression);
                    convertValue(expression);
                    for (String attributeValue:attributeValueList) {
                        if(expression.getValue().equals(attributeValue)) {
                            return true;
                        }
                    }
                    return false;
                case NOT_EQUAL:
                    checkParam(type, attribute, expression);
                    convertValue(expression);
                    for (String attributeValue:attributeValueList) {
                        if(expression.getValue().equals(attributeValue)) {
                            return false;
                        }
                    }
                    return true;
                case IN:
                    checkParam(type, attribute, expression);
                    convertValue(expression);
                    if (expression.getValue() instanceof List) {
                        for (String attributeValue:attributeValueList) {
                            if(((List)expression.getValue()).contains(attributeValue)) {
                                return true;
                            }
                        }
                        return false;
                    }
                    return false;
                case NOT_IN:
                    checkParam(type, attribute, expression);
                    convertValue(expression);
                    if (expression.getValue() instanceof List) {
                        for (String attributeValue:attributeValueList) {
                            if(((List)expression.getValue()).contains(attributeValue)) {
                                return false;
                            }
                        }
                        return true;
                    }
                    return false;
                case CONTAIN:
                    checkParam(type, attribute, expression);
                    for (String attributeValue:attributeValueList) {
                        if(attributeValue.contains((String)expression.getValue())) {
                            return true;
                        }
                    }
                    return false;
                case NOT_CONTAIN:
                    checkParam(type, attribute, expression);
                    for (String attributeValue:attributeValueList) {
                        if (attributeValue.contains((String)expression.getValue())) {
                            return false;
                        }
                    }
                    return true;
                case START_WITH:
                    checkParam(type, attribute, expression);
                    String expressionValue = (String)expression.getValue();
                    if ("_bk_iam_path_".equals(attribute) && expressionValue.endsWith(",*/")) {
                        expressionValue = expressionValue.substring(0, expressionValue.length() - 3);
                    }
                    for (String attributeValue:attributeValueList) {
                        if(attributeValue.startsWith(expressionValue)) {
                            return true;
                        }
                    }
                    return false;
                case NOT_START_WITH:
                    checkParam(type, attribute, expression);
                    for (String attributeValue:attributeValueList) {
                        if(attributeValue.startsWith((String)expression.getValue())) {
                            return false;
                        }
                    }
                    return true;
                case END_WITH:
                    checkParam(type, attribute, expression);
                    for (String attributeValue:attributeValueList) {
                        if(attributeValue.endsWith((String)expression.getValue())) {
                            return true;
                        }
                    }
                    return false;
                case NOT_END_WITH:
                    checkParam(type, attribute, expression);
                    for (String attributeValue:attributeValueList) {
                        if(attributeValue.endsWith((String)expression.getValue())) {
                            return false;
                        }
                    }
                    return true;
                case LESS_THEN:
                    checkParam(type, attribute, expression);
                    for (String attributeValue:attributeValueList) {
                        if(NumberUtils.createBigDecimal(attributeValue).compareTo(NumberUtils.createBigDecimal((String)expression.getValue())) < 0) {
                            return true;
                        }
                    }
                    return false;
                case LESS_THAN_OR_EQUAL:
                    checkParam(type, attribute, expression);
                    for (String attributeValue:attributeValueList) {
                        if(NumberUtils.createBigDecimal(attributeValue).compareTo(NumberUtils.createBigDecimal((String)expression.getValue())) <= 0) {
                            return true;
                        }
                    }
                    return false;
                case GREATER_THAN:
                    checkParam(type, attribute, expression);
                    for (String attributeValue:attributeValueList) {
                        if(NumberUtils.createBigDecimal(attributeValue).compareTo(NumberUtils.createBigDecimal((String)expression.getValue())) > 0) {
                            return true;
                        }
                    }
                    return false;
                case GREATER_THAN_OR_EQUAL:
                    checkParam(type, attribute, expression);
                    for (String attributeValue:attributeValueList) {
                        if(NumberUtils.createBigDecimal(attributeValue).compareTo(NumberUtils.createBigDecimal((String)expression.getValue())) >= 0) {
                            return true;
                        }
                    }
                    return false;
                case ANY:
                    if (type == null) {
                        return true;
                    }

                    return instanceMap.get(type) != null;
                default:
                    throw new IamException(-1L, "Unrecognized expression operator " + expression.getOperator());
            }
        }
    }

    private static void convertValue(ExpressionDTO expression) {
        if (expression.getValue() == null) {
            return;
        }
        if (expression.getValue() instanceof List) {
            if(((List)expression.getValue()).size() == 0) {
                return;
            }
            if (!(((List)expression.getValue()).get(0) instanceof String)) {
                expression.setValue(((List)expression.getValue()).parallelStream().map(Object::toString).collect(Collectors.toList()));
            }
        } else if (!(expression.getValue() instanceof String)) {
            expression.setValue(expression.getValue().toString());
        }

    }

    private static void checkParam(String type, String attribute, ExpressionDTO expression) {
        if (type == null || attribute == null || expression.getValue() == null) {
            throw new IamException(-1L, "Equal must have field and value!");
        }
    }

    private static List<String> getAttribute(String attribute, InstanceDTO instance) {
        if (instance == null) {
            return Collections.emptyList();
        } else if (StringUtils.isNotBlank(attribute)) {
            switch (attribute) {
                case "id":
                    return Collections.singletonList(instance.getId());
                case "_bk_iam_path_":
                    if (instance.getPath() != null) {
                        StringBuilder sb = new StringBuilder(instance.getPath().toString());
                        return Collections.singletonList(sb.append(instance.getType()).append(",").append(instance.getId()).append("/").toString());
                    } else {
                        if (instance.getAttribute() != null && instance.getAttribute().get("_bk_iam_path_") != null && instance.getAttribute().get("_bk_iam_path_") instanceof List) {
                            List<String> pathList = new ArrayList();
                            if (((List)instance.getAttribute().get("_bk_iam_path_")).get(0) instanceof String) {
                                ((List)instance.getAttribute().get("_bk_iam_path_")).forEach(path -> {
                                    StringBuilder sb = new StringBuilder((String)path);
                                    pathList.add(sb.append(instance.getType()).append(",").append(instance.getId()).append("/").toString());
                                });
                            }
                            return pathList;
                        }
                        return Collections.emptyList();
                    }
                default:
                    return instance.getAttribute() != null && instance.getAttribute().get(attribute) != null ? Collections.singletonList(instance.getAttribute().get(attribute).toString()) : Collections.emptyList();
            }
        } else {
            return Collections.emptyList();
        }
    }

    public boolean isAllowed(String username, String action) {
        ActionDTO actionDTO = new ActionDTO();
        actionDTO.setId(action);
        ExpressionDTO expression = this.policyService.getPolicyByActionForExtend(username, actionDTO, (List)null);
        return expression != null && !(new ExpressionDTO()).equals(expression);
    }

    public AuthResultDTO isAllowed(String username, String action, InstanceDTO instance) {
        if (StringUtils.isNotBlank(instance.getId())) {
            AuthResultDTO authResultDTO = this.isAllowed(username, action, Collections.singletonList(instance));
            if(authResultDTO.getIsAllowedResources() != null && authResultDTO.getIsAllowedResources().contains(instance.getId())) {
                authResultDTO.setAuth(true);
            }
            return authResultDTO;
        } else {
            ActionDTO actionDTO = new ActionDTO();
            actionDTO.setId(action);
            ExpressionDTO expression = this.policyService.getPolicyByActionForExtend(username, actionDTO, Collections.emptyList());
            if (expression == null) {
                AuthResultDTO authResultDTO = new AuthResultDTO();
                authResultDTO.setAuth(false);
                return authResultDTO;
            } else {
                AuthResultDTO authResultDTO = new AuthResultDTO();
                Map<String, InstanceDTO> instanceMap = new HashMap(1);
                instanceMap.put(instance.getType(), instance);
                authResultDTO.setAuth(calculateExpression(instanceMap, expression));
                authResultDTO.setResult(expression.getResult());
                return authResultDTO;
            }
        }
    }

    public AuthResultDTO isAllowed(String username, String action, List<InstanceDTO> instanceList) {
        if (CollectionUtils.isEmpty(instanceList)) {
            AuthResultDTO authResultDTO = new AuthResultDTO();
            authResultDTO.setIsAllowedResources(Collections.emptyList());
            return authResultDTO;
        } else {
            ActionDTO actionDTO = new ActionDTO();
            actionDTO.setId(action);
            ExpressionDTO expression = this.policyService.getPolicyByActionForExtend(username, actionDTO, buildResourceList(instanceList));
            log.debug("Expression for action|{}|{}|{}|{}", new Object[]{username, action, instanceList, expression});
            if (expression == null || expression.getOperator() == null) {
                AuthResultDTO authResultDTO = new AuthResultDTO();
                authResultDTO.setIsAllowedResources(Collections.emptyList());
                authResultDTO.setResult(expression.getResult());
                return authResultDTO;
            } else {
                List<String> allowedInstanceList = (List)instanceList.parallelStream().filter((instance) -> {
                    Map<String, InstanceDTO> instanceMap = new HashMap(1);
                    instanceMap.put(instance.getType(), instance);
                    return calculateExpression(instanceMap, expression);
                }).map(InstanceDTO::getId).distinct().collect(Collectors.toList());
                log.debug("Allowed instance list|{}|{}|{}", new Object[]{username, action, allowedInstanceList});
                AuthResultDTO authResultDTO = new AuthResultDTO();
                Optional<InstanceDTO> instanceDTOOptional = instanceList.stream().filter(instanceDTO -> instanceDTO.getType().endsWith("-column")).findFirst();
                if(instanceDTOOptional.isPresent()) {
                    authResultDTO.setIsAllowedResources(allowedInstanceList);
                    authResultDTO.setResult(expression.getResult());
                } else {
                    allowedInstanceList = instanceList.stream().map(InstanceDTO::getId).collect(Collectors.toList());
                    authResultDTO.setIsAllowedResources(allowedInstanceList);
                    authResultDTO.setResult(expression.getResult());
                }
                return authResultDTO;
            }
        }
    }

    public boolean isAllowed(String username, String action, InstanceDTO selfInstance, List<InstanceDTO> dependentInstanceList) {
        if (CollectionUtils.isEmpty(dependentInstanceList)) {
            throw new IamException(-1L, "DependentInstanceList is required!");
        } else {
            ActionDTO actionDTO = new ActionDTO();
            actionDTO.setId(action);
            List<ResourceDTO> dependencyResource = new ArrayList();
            if (this.shouldGetDependentResourceAttr(dependentInstanceList)) {
                String systemId = ((InstanceDTO)dependentInstanceList.get(0)).getSystem();
                String type = ((InstanceDTO)dependentInstanceList.get(0)).getType();
                ResourceDTO resource = ResourceDTO.builder().system(systemId).type(type).idList(new ArrayList()).build();
                dependentInstanceList.forEach(instancex -> {
                    if (StringUtils.isNotBlank(instancex.getId())) {
                        resource.getIdList().add(instancex.getId());
                    }

                });
                dependencyResource.add(resource);
            }

            ExpressionWithResourceDTO expressionWithResource = null;
            if (dependencyResource.size() == 1 && ((ResourceDTO)dependencyResource.get(0)).getIdList().size() > BATCH_SIZE) {
                int page = 0;
                for(ResourceDTO resource = (ResourceDTO)dependencyResource.get(0); page * BATCH_SIZE < resource.getIdList().size(); ++page) {
                    List resourceIdList;
                    if ((page + 1) * BATCH_SIZE < resource.getIdList().size()) {
                        resourceIdList = resource.getIdList().subList(page * BATCH_SIZE, (page + 1) * BATCH_SIZE);
                    } else {
                        resourceIdList = resource.getIdList().subList(page * BATCH_SIZE, resource.getIdList().size());
                    }

                    List<ResourceDTO> pageDependencyResource = new ArrayList();
                    ResourceDTO pageResource = ResourceDTO.builder().system(resource.getSystem()).type(resource.getType()).idList(resourceIdList).build();
                    pageDependencyResource.add(pageResource);
                    ExpressionWithResourceDTO pageExpressionWithResource = this.policyService.batchGetPolicyAndAttribute(username, actionDTO, (ResourceDTO)null, pageDependencyResource);
                    if (pageExpressionWithResource == null) {
                        log.error("Null response while batch get policy and attribute!|{}|{}|{}", new Object[]{username, action, pageDependencyResource});
                        throw new IamException(-1L, "Error while batch get policy and attribute by page!");
                    }

                    if (page == 0) {
                        expressionWithResource = pageExpressionWithResource;
                    } else {
                        for (Map.Entry entry:pageExpressionWithResource.getInstanceMap().entrySet()) {
                            if(MapUtils.isNotEmpty((Map)expressionWithResource.getInstanceMap().get(entry.getKey()))) {
                                for (Map.Entry<String, List<InstanceDTO>> instanceInfoEntry:((Map<String, List<InstanceDTO>>)entry.getValue()).entrySet()) {
                                    if (CollectionUtils.isNotEmpty((Collection)((Map)expressionWithResource.getInstanceMap().get(entry.getKey())).get(instanceInfoEntry.getKey()))) {
                                        ((List)((Map)expressionWithResource.getInstanceMap().get(entry.getKey())).get(instanceInfoEntry.getKey())).addAll((Collection)instanceInfoEntry.getValue());
                                    } else {
                                        ((Map)expressionWithResource.getInstanceMap().get(entry.getKey())).put(instanceInfoEntry.getKey(), instanceInfoEntry.getValue());
                                    }
                                }
                            }
                        }
                    }
                }
            } else {
                expressionWithResource = this.policyService.batchGetPolicyAndAttribute(username, actionDTO, (ResourceDTO)null, dependencyResource);
            }

            if (expressionWithResource == null) {
                log.error("Null response while batch get policy and attribute!|{}|{}|{}|{}", new Object[]{username, action, selfInstance, dependentInstanceList});
                throw new IamException(-1L, "Error while batch get policy and attribute!");
            } else {
                ExpressionDTO expression = expressionWithResource.getExpression();
                Map<String, Map<String, List<InstanceDTO>>> systemTypeInstanceMap = expressionWithResource.getInstanceMap();
                Map<String, InstanceDTO> instanceMap = new ConcurrentHashMap();
                systemTypeInstanceMap.forEach((systemIdx, typeInstanceMap) -> {
                    typeInstanceMap.forEach((type, instanceList) -> {
                        instanceList.forEach((instance) -> {
                            instanceMap.put(instance.getId(), instance);
                        });
                    });
                });
                boolean pass = true;
                Map<String, InstanceDTO> finalInstanceMap = new ConcurrentHashMap();
                if (selfInstance != null) {
                    finalInstanceMap.put(selfInstance.getType(), selfInstance);
                }

                for (InstanceDTO instance:dependentInstanceList) {
                    if (StringUtils.isNotBlank(instance.getId()) && instanceMap.get(instance.getId()) != null) {
                        finalInstanceMap.put(instance.getType(), instanceMap.get(instance.getId()));
                    } else {
                        finalInstanceMap.put(instance.getType(), instance);
                    }

                    if (!calculateExpression(finalInstanceMap, expression)) {
                        pass = false;
                    }
                }
                return pass;
            }
        }
    }

    private boolean shouldGetDependentResourceAttr(List<InstanceDTO> dependentResources) {
        return !CollectionUtils.isEmpty(dependentResources) && dependentResources.stream().anyMatch((instance) -> {
            return StringUtils.isNotEmpty(instance.getId());
        });
    }

    public boolean validRequest(HttpServletRequest request) {
        try {
            String authHeader = request.getHeader("Authorization").trim();
            String sign;
            if (authHeader.startsWith("Basic")) {
                sign = authHeader.replace("Basic", "").trim();
                String authStr = new String(Base64.getDecoder().decode(sign), Charsets.UTF_8);
                String[] userAndPass = authStr.split(":");
                String username = userAndPass[0];
                String password = userAndPass[1];
                if ("bk_iam".equals(username) && this.tokenService.getToken().equals(password)) {
                    log.debug("IAM callback basic auth check success!");
                    return true;
                } else {
                    log.error("IAM callback basic auth check failed!");
                    return false;
                }
            } else if (authHeader.startsWith("Digest")) {
                sign = authHeader.replace("Digest", "").trim();
                log.error("IAM callback digest auth check not support!");
                return false;
            } else {
                sign = request.getHeader("X-Bk-IAM-Signature").trim();
                if (StringUtils.isNotBlank(sign)) {
                    log.error("IAM callback sign auth check not support!");
                    return false;
                } else {
                    log.error("Unknown IAM callback auth method!");
                    return false;
                }
            }
        } catch (Exception var8) {
            log.error("Error while check iam callback request!", var8);
            return false;
        }
    }
}
